import g from "./common/g"
import { ILyuConfig, IMongoConfig, IMysqlConfig, IService } from "./etc/LyuInterface";
import { LRedis } from "./storage/redis/LRedis";
import { utils } from "./util/utils";
import "./util/progressCtrl";
import { conf } from "./etc/conf";
import LNet from "./network/ws/LNet";
import LWeb, { koaCtx } from "./network/http/LWeb";
import LMongo from "./storage/mongo/LMongo";
import LMysql from "./storage/mysql/LMysql";
import { RedisKeys } from "./etc/RedisKeys";
import EventMgr from "./event/EventMgr";
import message from "./network/packet/message";
import { agent } from "./network/ws/agent";
import { RedisOptions } from "ioredis";
import LHttp from "./network/http/LHttp";
import Fn from "./common/Fn";


class lyu {
    public lRedis!: LRedis;

    public async start(config: ILyuConfig) {
        g.name = config.name;
        g.host = utils.getLocalIP();
        g.port = config.port ?? 0;

        this.lRedis = new LRedis(config.redis ?? { host: "127.0.0.1" });
        await this.lRedis.select(conf.redisDbIndex);

        let res = await this.checkService();
        if (!res) {
            return null;
        }
        this.startService();
        return this;
    }

    private startService() {
        this.lWeb = new LWeb(g.port);
        console.log(`[LWeb] 内部通信模块启动，开始监听[${g.port}]端口`);

        let loop = () => {
            this.lRedis.expire(RedisKeys.getServiceKey(g.name), 30);
            setTimeout(loop, 15);
        }
        loop()
    }

    exit(isAct: boolean) {
        this.close();
        if (isAct) {
            process.exit(0);
        }
    }

    close() {
        if (this.lRedis != null) {
            this.lRedis.del(RedisKeys.getServiceKey(g.name));
        }
    }

    private async getPortPromise() {
        let port = conf.startPort;
        let serviceList = await this.lRedis.keys(RedisKeys.getServiceGroupKeys("*"));
        if (serviceList) {
            for (let s of serviceList) {
                if (s == g.name) {
                    return {
                        error: `服务名[${g.name}]被占用，请重新启动服务`,
                        port: 0,
                    };
                }
                let c = await this.lRedis.get(s);
                if (c == null) {
                    continue;
                }
                let service: IService = utils.safeJson(c);
                if (service) {
                    port = Math.max(port, service.port);
                }
            }
        }
        return {
            port: port + 1,
        };
    }

    private async checkService() {
        if (this.lRedis == undefined) {
            console.log(`存储服务未启动，请稍后重试`);
            return false
        }

        let service: IService | null = await this.getService(g.name);
        if (service != null) {
            if (service.host != g.host) {
                console.log(`服务名[${g.name}]已经存在[${service.host}]，请确认服务名字是否重复`);
                return false
            }
        } else {
            service = { name: g.name, host: g.host, port: 0 }
        }

        let port = await this.getPortPromise();
        if (port.error) {
            console.log(port.error);
            return false;
        }
        service.port = port.port;
        g.port = port.port;
        await this.lRedis.setJson(RedisKeys.getServiceKey(g.name), service, 30);
        return true
    }

    private async getService(name: string): Promise<IService | null> {
        return await this.lRedis.getJson(RedisKeys.getServiceKey(name));
    }


    /**
     * 同步调用外部节点方法
     * @param name 节点名称
     * @param funcname 方法名称
     */
    async call(name: string, funcname: string, ...data: any): Promise<any> {
        let service = await this.getService(name);
        if (service == undefined) {
            console.log(`服务[${name}]不存在`);
            return
        }

        let d = JSON.stringify(data);
        let address = `http://${service.host}:${service.port}/${funcname}?data=${d}`;
        let ret = await LHttp.get(address)
        return Fn.safeJson(ret);
    }

    send(name: string, funcname: string, ...data: any | null) {
        (async () => {
            let service = await this.getService(name);
            if (service == undefined) {
                console.log(`服务[${name}]不存在`);
                return
            }

            let d = JSON.stringify(data);
            let address = `http://${service.host}:${service.port}/${funcname}?param=${d}`;
            LHttp.get(address)
        })()
    }

    /**
     * 广播
     *  用于向所有节点发送消息
     * @param funcname 方法名称
     * @param data 方法参数
     */
    broadcast(funcname: string, ...data: any | null) {
        // this.adapter.broadcast(funcname, data);
    }

    /**
     * 注册节点方法
     *  该方法暴漏节点方法供其他节点调用
     * @param cmd 方法名称
     * @param func 方法回调
     */
    on(cmd: string, func: (...data: any) => Promise<any>) {
        this.lWeb.on(cmd, async (data: any, ctx: koaCtx) => {
            let tmp = Fn.safeJson(data.data)
            return await func(...tmp)
        });
    }

    /**
     * 取消节点方法
     * @param cmd 方法名称
     */
    off(cmd: string) {

    }


    //////////////////////// NET //////////////////////////////
    private net!: LNet;

    startNet(port: number) {
        this.net = new LNet(port);
        console.log(`[LNet] Socket模块启动，开始监听[${port}]端口`);
    }

    netOn(cmd: string | number, func: (ag: agent, data: any) => void) {
        this.net.register(cmd, func);
    }

    agentList() {
        return this.net.agentList;
    }

    regPackage(msg: { [x: number]: any }) {
        message.msg = msg;
    }

    /////////////////////////  EVENT    /////////////////////////////
    public event: EventMgr = new EventMgr();

    ///////////////////////////  HTTP  //////////////////////////
    private lWeb!: LWeb;

    startWeb(port: number): boolean {
        this.lWeb = new LWeb(port);
        console.log(`[LWeb] http模块启动，开始监听[${port}]端口`);
        return true
    }

    webOn(key: string, func: (d: any, c: koaCtx) => Promise<any>) {
        this.lWeb.on(key, func);
    }

    webPost(key: string, func: (d: any, c: koaCtx) => Promise<any>) {
        this.lWeb.post(key, func);
    }

    // webCors(){
    //     this.web.cors();
    // }

    //////////////////////// Redis //////////////////////
    public redisList: LRedis[] = []

    async startRedis(conf?: RedisOptions): Promise<number> {
        let rds = new LRedis(conf ? conf : { host: "127.0.0.1" });
        try {
            let info = await rds.info()
            this.redisList.push(rds)
            return this.redisList.length - 1
        } catch {
        }
        return -1
    }

    redis(index = 0) {
        return this.redisList[index];
    }

    ////////////////////////  Mysql /////////////////////
    public mysql: LMysql[] = [];

    startMysql(conf: IMysqlConfig) {
        let conn = new LMysql(conf);
        this.mysql.push(conn);
        return this.mysql.length - 1;
    }

    ////////////////////////  Mongo /////////////////////
    public mongoList: LMongo[] = [];

    async startMongo(conf?: IMongoConfig): Promise<number> {
        let m = await LMongo.start(conf);
        this.mongoList.push(m);
        return this.mongoList.length - 1;
    }

    mongo(index: number = 0): LMongo {
        return this.mongoList[index];
    }
}

export default new lyu();
export { LRedis, LMongo, LMysql }